package com.appleyk.nio;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * <p></p>
 *
 * @author appleyk
 * @version V.0.1.1
 * @blob https://blog.csdn.net/appleyk
 * @github https://github.com/kobeyk
 * @date created on 8:30 2021/2/2
 */
public class NIOChatServer {

    private static Logger logger = LoggerFactory.getLogger(NIOChatServer.class);

    private static final int port = 6666;
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;

    // 初始化
    public NIOChatServer() {
        try{
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            // 忘了最最最重要的一点，注册监听事件啊
            serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    // 监听
    private void listen()throws IOException{
        logger.info("服务端开启监听...");
        while (true){
            int selectCount = selector.select(3000);
            if (selectCount == 0){
                continue;
            }
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()){
                SelectionKey key = keyIterator.next();
                if (key.isAcceptable()){
                    SocketChannel channel = serverSocketChannel.accept();
                    logger.info("客户端{} 已上线",channel.getRemoteAddress());
                    channel.configureBlocking(false);
                    channel.register(selector,SelectionKey.OP_READ);
                }else if (key.isReadable()){
                    readHandler(key);
                }
                keyIterator.remove();
            }
        }
    }

    private void readHandler(SelectionKey key){
        SocketChannel channel = null;
        try{
            channel = (SocketChannel) key.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            // 可能客户端通道还存在，但是客户端却断开连接了，因此下面从通道读可能抛异常
            int read = channel.read(byteBuffer);
            // 这里得判断一下
            if (read == -1){
                logger.info("{} 退出群聊...",channel.getRemoteAddress());
                // 那就要取消key和关闭channel了
                key.cancel();
                channel.close();
            }
            if (read > 0){
                String info = new String(byteBuffer.array());
                logger.info("from 客户端 :{}",info);
                // 下面实现消息转发
                logger.info("from 服务端 :开始转发客户端消息...");
                send2Others(info,channel);
            }
        }catch (IOException e){
           try{
               logger.info("客户端{} 退出群聊...",channel.getRemoteAddress());
               // 那就要取消key和关闭channel了
               key.cancel();
               channel.close();
           }catch (IOException e1){
                logger.error(e1.getMessage());
           }
        }
    }

    private void send2Others(String info,SocketChannel self) throws IOException{
       // 首先肯定从服务端的选择器中选择所有注册在内的key了（除了下线cancel的不算）
        Set<SelectionKey> keys = selector.keys();
        for (SelectionKey key : keys) {
            // 取出key对应的channel（注意，这里面也包括服务端自己，但是服务端类型是ServerSocketChannel）
            Channel channel = key.channel();
            // 如果通道是客户端类型的，且通道不是发消息方，则，服务端这边统一往符合条件的客户端通道里写数据>>info
            if(channel instanceof SocketChannel && !channel.equals(self)){
                 // 转一下
                SocketChannel otherChannel = (SocketChannel)channel;
                // 直接将消息包装成byte缓冲区
                ByteBuffer byteBuffer = ByteBuffer.wrap(info.getBytes());
                // 如果客户端通道在写的过程中发生断开异常，就交给上游的调用来类处理
                otherChannel.write(byteBuffer);
            }
        }
    }

    public static void main(String[] args) throws IOException{
        NIOChatServer chatServer = new NIOChatServer();
        chatServer.listen();
    }
}
